{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Introduction to ChipWhisperer Husky\n",
    "\n",
    "This notebook highlights the new features of CW-Husky. It's written for users who are already familiar with the previous generations of ChipWhisperer capture hardware (CW-Lite or CW-Pro).\n",
    "\n",
    "If you haven't used CW-Lite or Pro before, then start with our [tutorials](../courses/README.md), which you'll find under `jupyter/courses/`, then come back here to learn about Husky's additional features which aren't covered in those tutorials.\n",
    "\n",
    "First, let's connect:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "SCOPE=\"OPENADC\"\n",
    "PLATFORM=\"CWHUSKY\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import chipwhisperer as cw\n",
    "%run \"../../Setup_Scripts/Setup_Generic.ipynb\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1. Errors\n",
    "\n",
    "Not the most exciting change, but important to know! Husky detects and reports a number of errors, and most of these errors cause the red \"ADC\" and \"Glitch\" LEDs to flash.\n",
    "\n",
    "If you see red LEDs flashing, print `scope.errors` to see why:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.errors"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Each of these 4 classes of errors can be accessed individually:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.XADC.status"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.adc.errors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.clock.extclk_error"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.trace.errors"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The different types of errors are covered in their corresponding sections below.\n",
    "\n",
    "The error flags and the red flashing lights can be cleared by calling:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.errors.clear()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2. Sampling and Streaming\n",
    "\n",
    "Husky has a faster ADC which supports sampling at up to 200 MS/s. Its resolution is also increased, to 12 bits/sample.\n",
    "\n",
    "You have the option of reducing this to 8 bits/sample. This doesn't increase how many ADC samples can be collected when not streaming, but it does make reading the ADC samples faster, and this allows better streaming performance (higher sampling rate and/or more samples)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.adc.bits_per_sample"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Streaming performance depends on a lot of factors, but capturing \"unlimited\" samples should be possible at up to 16 MS/s in 12-bit mode, or 24 MS/s in 8-bit mode.\n",
    "\n",
    "You should be able to sample a bit faster if the total number of samples is not too much more than Husky's internal sample storage capacity (131070 samples).\n",
    "\n",
    "If you try to sample too much data / too fast in streaming mode, you'll get an error message about some FIFO over- or underflow. The details of the error message are intended for developers to understand exactly where Husky \"gave up\". As a user, just dial down the sampling rate and/or number of samples until captures are reliably successful.\n",
    "\n",
    "Husky monitors the ADC samples on-the-fly during a capture and will flag errors if clipping occurs (`scope.gain.db` is too high) or if not enough of the dynamic range is used (`scope.gain.db` is too low). These checks are enabled by default, but they can be disabled by calling:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.adc.clip_errors_disabled = True\n",
    "scope.adc.lo_gain_errors_disabled = True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you care about power measurements on very specific clock cycles, you need to be aware Husky has a lower sampling latency than CW-Lite or CW-Pro: samples are collected 3 samples earlier.\n",
    "\n",
    "You can compensate for this by setting a sampling offset:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.adc.offset = 3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You only need to do this if, for example, you previously developed an attack using CW-Lite/Pro which looks at power samples on specific clock cycles (such as our [CW305 ECC](CW305_ECC/CW305_ECC_part1.ipynb) series of demos).\n",
    "\n",
    "For attacks that use the power trace from the entire target operation (for example, our AES CPA and DPA attacks), then you don't need to worry about this."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 3. Clocking\n",
    "\n",
    "Husky now uses an external PLL to generate the target and ADC sampling clock (aka \"clkgen\"), which affords more flexibility in setting the ADC sampling frequency.\n",
    "\n",
    "The clkgen frequency can be set as usual:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.clock.clkgen_freq = 10e6"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The sampling clock multiplier, which was previously restricted to `clkgen_x1` or `clkgen_x4` (or `extclk_x1` / `extclk_x4`) can now be any integer. So if you want 10 samples per clock period, do:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.clock.adc_mul = 10"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you are sourcing the sampling clock from your target (i.e. `scope.clock.clkgen_src = 'extclk'`), then you need to inform Husky of that external clock frequency by setting `scope.clock.clkgen_freq` accordingly.\n",
    "\n",
    "If the external clock frequency changes, the sampling clock may not be generated properly. Husky will notice this clock change for you and flash the \"Armed\" and \"Capturing\" LEDs, and set `scope.clock.extclk_error` to True. If this happens, update `scope.clock.clkgen_freq` and clear the error.\n",
    "\n",
    "The relative phase between the target clock and the ADC sampling clock can be adjusted via `scope.clock.adc_phase`. This was also possible on CW-Lite/Pro, but with Husky the step sizes and the range are different. Go through the [07 - Husky Sampling Phase.ipynb](07%20-%20Husky%20Sampling%20Phase.ipynb) notebook to learn more about this."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4. Segmented Captures\n",
    "\n",
    "\"Segmented captures\" are captures where instead of capturing `scope.adc.samples` samples continously, Husky will capture a number of \"chunks\", or \"segments\", of `scope.adc.samples` samples.\n",
    "\n",
    "The number of segments to capture is specified by `scope.adc.segments`. There are two ways that segmented captures are intended to be used:\n",
    "\n",
    "1. The start of each segment comes from a trigger event. This is done by setting `scope.adc.segment_cycle_counter_en = False`. This can be used to drastically speed up captures, especially for captures that are not very long, because it allows a single arm + capture to record multiple traces, instead of doing an arm + capture for each individual trace. The target firmware must be modified to accomodate this.\n",
    "\n",
    "2. The start of each segment is kicked off periodically every `scope.adc.segment_cycles` cycles. This is done by setting `scope.adc.segment_cycle_counter_en = True`. This can be useful for very long target operations that are composed of periodic, constant-time sub-operations. Public-key operations where each secret bit is processed in constant time are an example of this. The [CW305 ECC](CW305_ECC/CW305_ECC_part1.ipynb) series of demos shows an actual real-life example of this. In these cases, the advantages of using segments are that (1) your traces will require less storage, and (2) you can avoid streaming and its sampling rate limitations.\n",
    "\n",
    "There is one non-obvious gotcha when using segments: the total capture size (i.e. `scope.adc.segments * scope.adc.samples`) cannot exceed 98352 samples, which is less than the maximum capture size when segmenting is not in use.\n",
    "\n",
    "Beyond this, be aware that it's quite possible to set up `scope.adc` settings which result in some `scope.adc.errors`. Usually, the error would be \"segmenting error\", and most illegal scenarios have to do with segments being \"too close\" to each other. It's hard to precisely define \"too close\" because it depends on several settings. If you run into this error, either (a) you've mis-specified your segment settings, or (b) your segments are too close together, so either push them further apart, or realize that it's not worth using segments for your use-case."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 5. MCX Ports\n",
    "\n",
    "Husky adds a pair of MCX connectors: AUX In/Out and Trigger/Glitch Out.\n",
    "\n",
    "The AUX I/O MCX can be programmed to be an input or an output.\n",
    "\n",
    "As an input, it can be used as an alternative trigger input (i.e. like IO4) or as an alternative clock input (i.e. like HS1)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.io.aux_io_mcx = 'high_z' # set to input\n",
    "# choose one:\n",
    "#scope.trigger.triggers = 'aux'\n",
    "#scope.clock.clkgen_src = 'extclk_aux_io'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As an output, it mirrors the clock that is output on the HS2 pin:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.io.aux_io_mcx = 'hs2'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Trigger/Glitch Out MCX can be set to output the glitch output signal (same as `scope.glitch.output`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.io.glitch_trig_mcx = 'glitch'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "or it can output the internal ADC capture trigger signal, i.e. to trigger an external oscilloscope to start capturing at the same time that Husky would start capturing:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.io.glitch_trig_mcx = 'trigger'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 6. USERIO Header\n",
    "\n",
    "In addition to the side 20-pin connector which has the same I/Os as the side 20-pin connector on the CW-Lite/Pro, Husky has a second 20-pin connnector on its front panel which can serve several different purposes.\n",
    "\n",
    "### It can be used to bit-bang data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.userio.mode = 'normal'\n",
    "# write:\n",
    "scope.userio.direction = 0xff # set all 9 pins to be driven by Husky\n",
    "scope.userio.drive_data = 0xaa\n",
    "scope.userio.drive_data = 0x55\n",
    "scope.userio.direction = 0\n",
    "# read:\n",
    "stat = scope.userio.status\n",
    "for i in range(9):\n",
    "    print(\"Pin %d status: %d\" % (i, (stat >> i) & 0x1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### It can be used to debug your target via Husky:\n",
    "\n",
    "See https://chipwhisperer.readthedocs.io/en/latest/debugging.html to learn more about this feature, which is also supported by CW-Lite/Pro.\n",
    "\n",
    "With Husky, you can connect a 20-pin ARM debug cable from your target's debug port to the USERIO port for either JTAG or SWD debugging, then just set either:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.userio.mode = 'target_debug_swd'\n",
    "#scope.userio.mode = 'target_debug_jtag'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### It can be used for TraceWhisperer:\n",
    "\n",
    "For sniffing or triggering on Arm debug trace data in either SWD or parallel trace mode, with the following connections:\n",
    "\n",
    "| Target pin | Husky pin|\n",
    "|------------|------|\n",
    "| TMS        | D0   |\n",
    "| TCK        | D1   |\n",
    "| TDO        | D2   |\n",
    "| unused     | D3   |\n",
    "| TRACEDATA0 | D4   |\n",
    "| TRACEDATA1 | D5   |\n",
    "| TRACEDATA2 | D6   |\n",
    "| TRACEDATA3 | D7   |\n",
    "| TRACECLOCK | CK   |\n",
    "\n",
    "See the TraceWhisperer section below for more about this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.userio.mode = 'trace'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### It can be used as a trigger module input."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.trigger.triggers = 'userio_d0 and tio2'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### It can be used for to debug sequenced trigger settings:\n",
    "\n",
    "When sequencing multiple triggers, it can be hard to set up trigger parameters correctly; the USERIO header can give you visibility into what's happening inside Husky and help you figure out whether triggers are firing when you expect them to:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.userio.mode = 'fpga_debug'\n",
    "scope.userio.fpga_mode = 13\n",
    "print(scope.userio)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### It can be used for debugging FPGA development:\n",
    "\n",
    "Xilinx ILAs are great, but sometimes it's easier to route debug signals to a header that can be probed by an external logic analyzer. (Especially when BRAMs utilization is at 96% in the Husky FPGA.)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Beyond this, the main idea of the USERIO header is for *users* to come up with their own uses for it. TraceWhisperer is a fairly complex example where trace data comes in on the USERIO port and is pattern-matched to generate ADC capture triggers. If you need to trigger on some other esoteric protocol, then USERIO is there for you. Scroll down to the \"Development\" section to learn how you can customize Husky code."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 7. Triggering\n",
    "\n",
    "Husky provides more triggering options:\n",
    "\n",
    "- ADC level-based triggering\n",
    "- full-fledged UART triggering, with programmable patterns and wildcards\n",
    "- TraceWhisperer triggering\n",
    "- SAD triggering like CW-Pro\n",
    "- edge count triggering\n",
    "- for basic, UART, and edge count triggering: ability to use the USERIO pins as a trigger module input\n",
    "\n",
    "Husky also timestamps trigger events, which can be helpful when tuning trigger parameters.\n",
    "\n",
    "See [02 - Husky Triggers.ipynb](02%20-%20Husky%20Triggers.ipynb) for details on these, except for TraceWhisperer triggering (scroll down to the TraceWhisperer section for that).\n",
    "\n",
    "Additionally, Husky can be set to use a *sequence* of triggers, which is when multiple triggers must fire in a specified sequence in order to trigger a capture (or glitch). See [04 - Husky Trigger Sequencer.ipynb](04%20-%20Husky%20Trigger%20Sequencer.ipynb) to learn how to use this feature."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 8. Glitching\n",
    "\n",
    "There's a whole notebook explaining how glitching in Husky is different (and better!): [03 - Husky Glitching](03%20-%20Husky%20Glitching.ipynb).\n",
    "\n",
    "One new feature not covered in that notebook is multiple glitches. CW-Lite/Pro have the ability to generate glitches on multiple consecutive clock cycles with `scope.glitch.repeat`.\n",
    "\n",
    "Husky adds `scope.glitch.num_glitches`. When this is greater than one, then `scope.glitch.repeat` and `scope.glitch.ext_offset` become arrays of length `scope.glitch.num_glitches`.\n",
    "\n",
    "For example, if  `scope.glitch.num_glitches = 3`, then the first glitch will be issued `scope.glitch.ext_offset[0]` cycles after the glitch trigger event and last `scope.glitch.repeat[0]` cycles.\n",
    "\n",
    "The next glitch will be issued `scope.glitch.ext_offset[1]+1` cycles after the start of the first glitch and last `scope.glitch.repeat[1]` cycles.\n",
    "\n",
    "Finally, the last glitch will be issued `scope.glitch.ext_offset[2]`+1 cycles after the start of the second glitch and last `scope.glitch.repeat[2]` cycles.\n",
    "\n",
    "To illustrate, with these settings, the generated glitches would be as shown:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.glitch.num_glitches = 4\n",
    "scope.glitch.ext_offset = [0, 0, 1, 2]\n",
    "scope.glitch.repeat     = [1, 1, 2, 3]\n",
    "scope.glitch.output = 'enable_only'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![offset](img/multiple_glitches.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It is possible to specify an illegal set of multiple glitches: if `repeat[i]` is greater than `ext_offset[i+1]+1`, then glitch i+1 won't be generated as expected, and glitch i will be held high for the maximum number of repeat clock cycles (8192). If you're doing voltage glitching, beware, since this could damage your hardware. If unsure, test your glitch parameters before setting `scope.io.glitch_hp` or `scope.io.glitch_lp`.\n",
    "\n",
    "When illegal glitch parameters are used, the glitch module will likely get into a stuck state, which is indicated by `scope.glitch.state` returning \"done\". You will need to reset the glitch state machine by setting `scope.glitch.state = 0`, which should return the state to \"idle\". Then, fix your glitch parameters and try again."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 9. Logic Analyzer\n",
    "\n",
    "If you went through [03 - Husky Glitching](03%20-%20Husky%20Glitching.ipynb) or [07 - Husky Sampling Phase.ipynb](07%20-%20Husky%20Sampling%20Phase.ipynb), then you already know that Husky contains a small 9-channel internal logic analyzer. But you may not know that it can be used to capture more than just glitch signals. While this will never replace an external logic analyzer, you might find it useful.\n",
    "\n",
    "In addition to glitch signals, Husky's logic analyzer can capture (most) signals from the side 20-pin connector, all signals from the front USERIO connector, as well as internal debug signals (the latter is intended for FPGA debug/development).\n",
    "\n",
    "The logic analyzer can be triggered by the ADC capture trigger, the HS1 input clock, or a rising or falling edge on any of the USERIO D0-D7 pins.\n",
    "\n",
    "Its sampling clock is sourced from the clock specified by `scope.LA.clk_source`: either the target HS1 clock, Husky's generated clock, or the internal 96 MHz USB clock. This clock is multiplied by `scope.LA.oversampling_factor` to obtain the actual sampling clock. Officially, the maximum sampling clock is 250 MHz; in practice, up to 400 MHz seems to work well, although you will receive a warning if you set a clock greater than 250 MHz.\n",
    "\n",
    "For slower data (like UART), `scope.LA.downsample` allows you to apply downsampling.\n",
    "\n",
    "The `scope.LA.capture_depth` parameter controls how many samples are collected for each of the 9 channels (maximum 16376 samples, per channel).\n",
    "\n",
    "Look at [03 - Husky Glitching](03%20-%20Husky%20Glitching.ipynb) to learn how to read the captured data."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 10. TraceWhisperer\n",
    "\n",
    "TraceWhisperer is an Arm Coresight debug trace sniffer which can be used to unobstrusively learn what an Arm target is doing and trigger captures or glitches from this.\n",
    "\n",
    "It was originally developed for the [CW305 FPGA target platform](https://github.com/newaetech/DesignStartTrace), ported to the [PhyWhisperer platform](https://github.com/newaetech/tracewhisperer), and now we've squeezed it into Husky for a single-board solution.\n",
    "\n",
    "There are a series of notebooks in the [DesignStartTrace repository](https://github.com/newaetech/DesignStartTrace/tree/master/jupyter) to learn how to use it.\n",
    "\n",
    "On Husky, the only limitation is that the logic analyzer cannot be used at the same time because they share the same common storage."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 11. Temperature and Voltage Monitoring\n",
    "\n",
    "The Husky FPGA has an XADC module which continuously monitors and reports FPGA temperatures and voltages."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.XADC"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If any of these get out of range, an error is flagged and the red LEDs are flashed. Read `scope.XADC.status` to see the error.\n",
    "\n",
    "Additionally, the SAD module and all of the FPGA MMCMs (PLLs) are shutdown since they are relatively power-hungry. MMCMs are used to generate glitches, the logic analyzer sampling clock, the TraceWhisperer SWO sampling clock, and the trace clock phase shifting.\n",
    "\n",
    "The VCC alarm limits are set as per Xilinx's recommended operating conditions.\n",
    "\n",
    "There are two sets of temperature alarms: the \"device\" temperature alarm is fixed at 85 celcius and cannot be changed. The \"user\" temperature alarm defaults to 80 celcius and can be changed by setting `scope.XADC.temp_trigger`.\n",
    "\n",
    "VCC and temperature alarms are \"sticky\": when they occur, they remain set even when the condition that triggered the alarm returns to a normal level, until the error condition is manually cleared with `scope.XADC.status = 0`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 12. Development\n",
    "\n",
    "Finally, one more thing that's improved with Husky is the infrastructure to facilitate FPGA development.\n",
    "\n",
    "Husky's FPGA code is [here](https://github.com/newaetech/chipwhisperer-husky-fpga), and the repository's README gives an overview of the verification infrastructure that you can use to validate your changes.\n",
    "\n",
    "Husky's SAM3U firmware code is [here](https://github.com/newaetech/chipwhisperer-husky)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
